//----------------------------------------------------------------
//module name : yhz_control_and_state_register
//engineer : yhz
//date : 2021.09.15
//----------------------------------------------------------------
`include "yhz_defines.v"
module yhz_control_and_state_register (
    input  wire        i_clk             ,
    input  wire        i_rst             ,
    input  wire        i_hand_shake_flag ,
    //trap
    input  wire        i_trap_ecall      ,
    output wire        o_trap_timer      ,
    output wire        o_trap            ,
    input  wire        i_mret            ,
    input  wire [63:0] i_pc_addr         ,
    output wire [63:0] o_mtvec_addr      ,
    output wire [63:0] o_mepc_addr       ,
    //clint
    input  wire        i_w_mtime_en      ,
    input  wire        i_r_mtime_en      ,
    input  wire        i_w_mtimecmp_en   ,
    input  wire        i_r_mtimecmp_en   ,
    input  wire [63:0] i_w_clint_data    ,
    output wire [63:0] o_r_clint_data    ,
    //write
    input  wire        i_w_csr_en        ,
    input  wire [11:0] i_w_csr_addr      ,
    input  wire [63:0] i_w_csr_data      ,
    //read
    input  wire        i_r_csr_en        ,
    input  wire [11:0] i_r_csr_addr      ,
    output wire [63:0] o_r_csr_data      ,
    //difftest
    output wire [63:0] diff_mstatus      ,
    output wire [63:0] diff_mepc         ,
    output wire [63:0] diff_mtvec        ,
    output wire [63:0] diff_mcause       ,
    output wire [63:0] diff_mip          ,
    output wire [63:0] diff_mie          ,
    output wire [63:0] diff_mscratch      
);
//----------------------------------------------------------------
//register & wire
//----------------------------------------------------------------
    //ecall
    reg  [63:0] mstatus    ;
    reg  [63:0] mtvec      ;
    reg  [63:0] mepc       ;
    reg  [63:0] mcause     ;
    //timer
    reg  [63:0] mip        ;
    reg  [63:0] mie        ;
    reg  [63:0] mcycle     ;
    reg  [63:0] mtime      ;
    reg  [63:0] mtimecmp   ;
    //rtt
    reg  [63:0] mscratch   ;

    reg         trap_timer ;
    reg  [63:0] r_csr_data ;
    wire        timer_trap_en   = mstatus[3] & mie[7]                                       ;
    wire [63:0] r_clint_data    = i_r_mtime_en ? mtime : i_r_mtimecmp_en ? mtimecmp : 64'd0 ;
    wire [63:0] mtvec_addr      = trap         ? mtvec : 64'd0                              ;
    wire [63:0] mepc_addr       = i_mret       ? mepc  : 64'd0                              ;
    wire        trap            = i_trap_ecall | trap_timer                                 ;
    wire        trap_timer_flag = trap_timer   & i_hand_shake_flag                          ;
    wire        trap_flag       = i_trap_ecall | trap_timer_flag                            ; 
//----------------------------------------------------------------
//logic
//----------------------------------------------------------------
    //trap_timer
    always @(posedge i_clk) begin
        if(i_rst) begin
            trap_timer <= 1'b0 ;
        end
        else if(timer_trap_en & (mtime == mtimecmp)) begin
            trap_timer <= 1'b1 ;
        end
        else if(i_hand_shake_flag) begin
            trap_timer <= 1'b0 ;
        end
        else begin
            trap_timer <= trap_timer ;
        end
    end
    //mstatus
    always @(posedge i_clk) begin
        if(i_rst) begin
            mstatus <= {1'b0 , 50'd0 , 2'b11 , 11'd0} ;
        end
        else if(i_mret) begin
            mstatus[3]     <= mstatus[7] ;
            mstatus[7]     <= 1'b1       ;
            mstatus[12:11] <= 2'b00      ;
        end
        else if(trap_flag) begin
            mstatus[7]     <= mstatus[3] ;
            mstatus[3]     <= 1'b0       ;
            mstatus[12:11] <= 2'b11      ;
        end
        else if(i_w_csr_en && (i_w_csr_addr == 12'h300)) begin
            mstatus <= {i_w_csr_data[13] & i_w_csr_data[14] , i_w_csr_data[62:0]} ;
        end
        else begin
            mstatus <= mstatus ;
        end
    end
    //mtvec
    always @(posedge i_clk) begin
        if(i_rst) begin
            mtvec <= 64'd0 ;
        end
        else if(i_w_csr_en && (i_w_csr_addr == 12'h305)) begin
            mtvec <= {i_w_csr_data[63:2] , mtvec[1:0]} ;
        end
        else begin
            mtvec <= mtvec ;
        end
    end
    //mepc
    always @(posedge i_clk) begin
        if(i_rst) begin
            mepc <= 64'd0 ;
        end
        else if(trap_flag) begin
            mepc <= i_pc_addr ;
        end
        else if(i_w_csr_en && (i_w_csr_addr == 12'h341)) begin
            mepc <= i_w_csr_data ;
        end
        else begin
            mepc <= mepc ;
        end
    end
    //mcause
    always @(posedge i_clk) begin
        if(i_rst) begin
            mcause <= 64'd0 ;
        end
        else if(i_trap_ecall) begin
            mcause <= 64'h00000000_0000000b ;
        end
        else if(trap_timer_flag) begin
            mcause <= 64'h80000000_00000007 ;
        end
        else if(i_w_csr_en && (i_w_csr_addr == 12'h342)) begin
            mcause <= i_w_csr_data ;
        end
        else begin
            mcause <= mcause ;
        end
    end
    //mip
    always @(posedge i_clk) begin
        if(i_rst) begin
            mip <= 64'd0 ;
        end
        else if(i_w_csr_en && (i_w_csr_addr == 12'h344)) begin
            mip <= i_w_csr_data ;
        end
        else begin
            mip <= mip ;
        end
    end
    //mie
    always @(posedge i_clk) begin
        if(i_rst) begin
            mie <= 64'd0 ;
        end
        else if(i_w_csr_en && (i_w_csr_addr == 12'h304)) begin
            mie <= i_w_csr_data ;
        end
        else begin
            mie <= mie ;
        end
    end
    //mcycle
    always @(posedge i_clk) begin
        if(i_rst) begin
            mcycle <= 64'd0 ;
        end
        else begin
            mcycle <= mcycle + 1'b1 ;
        end
    end
    //mtime
    always @(posedge i_clk) begin
        if(i_rst) begin
            mtime <= 64'd0 ;
        end
        else if(i_w_mtime_en) begin
            mtime <= i_w_clint_data ;
        end
        else if(timer_trap_en) begin
            mtime <= mtime + 1'b1 ;
        end
        else begin
            mtime <= mtime ;
        end
    end
    //mtimecmp
    always @(posedge i_clk) begin
        if(i_rst) begin
            mtimecmp <= 64'd0 ;
        end
        else if(i_w_mtimecmp_en) begin
            mtimecmp <= i_w_clint_data ;
        end
        else begin
            mtimecmp <= mtimecmp ;
        end
    end
    //mscratch
    always @(posedge i_clk) begin
        if(i_rst) begin
            mscratch <= 64'd0 ;
        end
        else if(i_w_csr_en && (i_w_csr_addr == 12'h340)) begin
            mscratch <= i_w_csr_data ;
        end
        else begin
            mscratch <= mscratch ;
        end
    end
    //rs1_data
    always @(*) begin
        if(i_rst) begin
            r_csr_data = 64'd0 ;
        end
        else if(i_w_csr_en) begin
            case(i_r_csr_addr)
                12'h300 : r_csr_data = mstatus  ;
                12'h304 : r_csr_data = mie      ;
                12'h305 : r_csr_data = mtvec    ;
                12'h340 : r_csr_data = mscratch ;
                12'h341 : r_csr_data = mepc     ;
                12'h342 : r_csr_data = mcause   ;
                12'h344 : r_csr_data = mip      ;
                12'hb00 : r_csr_data = mcycle   ;
                default : r_csr_data = 64'd0    ;
            endcase
        end
        else begin
            r_csr_data = 64'd0 ;
        end
    end
//----------------------------------------------------------------
//output
//----------------------------------------------------------------
    assign o_r_csr_data   = r_csr_data   ;
    assign o_r_clint_data = r_clint_data ;
    assign o_mtvec_addr   = mtvec_addr   ;
    assign o_mepc_addr    = mepc_addr    ;
    assign o_trap         = trap         ;
    assign o_trap_timer   = trap_timer   ;
//----------------------------------------------------------------
//difftest
//----------------------------------------------------------------
    assign diff_mstatus  = mstatus  ;
    assign diff_mepc     = mepc     ;
    assign diff_mtvec    = mtvec    ;
    assign diff_mcause   = mcause   ;
    assign diff_mip      = mip      ;
    assign diff_mie      = mie      ;
    assign diff_mscratch = mscratch ;
//----------------------------------------------------------------
endmodule
//----------------------------------------------------------------
